//
// Created by zengao on 2022/12/29.
//

#include "Lexer.h"
#include "Diag.h"
#include <cassert>

void C100::Lexer::getNextChar() {
    if (cursor == sourceCode.size()) {
        curChar = '\0';
        cursor++;
    } else {
        curChar = sourceCode[cursor++];
    }

}

void C100::Lexer::getNextToken() {
    while (std::isspace(curChar)) {
        if (curChar == '\n') {
            line++;
            lineHead = cursor;
        }
        getNextChar();
    }
    int startPos = cursor - 1;

    TokenKind kind;
    SourceLocation location;
    location.line = line;
    location.col = cursor - 1 - lineHead;
    int value = 0;
    if (curChar == '\0') {
        kind = TokenKind::Eof;
    } else if (curChar == '+') {
        kind = TokenKind::Add;
        getNextChar();
    } else if (curChar == '-') {
        kind = TokenKind::Sub;
        getNextChar();
    } else if (curChar == '*') {
        kind = TokenKind::Mul;
        getNextChar();
    } else if (curChar == '/') {
        kind = TokenKind::Div;
        getNextChar();
    } else if (curChar == '{') {
        kind = TokenKind::LBrace;
        getNextChar();
    } else if (curChar == '}') {
        kind = TokenKind::RBrace;
        getNextChar();
    } else if (curChar == '(') {
        kind = TokenKind::LParent;
        getNextChar();
    } else if (curChar == ')') {
        kind = TokenKind::RParent;
        getNextChar();
    } else if (curChar == ';') {
        kind = TokenKind::Semicolon;
        getNextChar();
    } else if (curChar == '=') {
        if (peekChar(1) == '=') {
            getNextChar();
            kind = TokenKind::Equal;
        } else {
            kind = TokenKind::Assign;
        }
        getNextChar();
    } else if (curChar == '!') {
        if (peekChar(1) == '=') {
            getNextChar();
            kind = TokenKind::PipeEqual;
        } else {
            diagE(sourceCode, location.line, location.col, "current '%c' is illegal", curChar);
        }
        getNextChar();
    } else if (curChar == '>') {
        if (peekChar(1) == '=') {
            getNextChar();
            kind = TokenKind::GreaterEqual;
        } else {
            kind = TokenKind::Greater;
        }
        getNextChar();
    } else if (curChar == '<') {
        if (peekChar(1) == '=') {
            getNextChar();
            kind = TokenKind::LesserEqual;
        } else {
            kind = TokenKind::Lesser;
        }
        getNextChar();
    } else if (std::isdigit(curChar)) {
        kind = TokenKind::Num;
        value = 0;
        do {
            value = value * 10 + curChar - '0';
            getNextChar();
        } while (isdigit(curChar));
    } else {
        if (isLetter()) {
            while (isLetterOrDigit()) {
                getNextChar();
            }
            kind = TokenKind::Identifier;
            std::string_view content = sourceCode.substr(startPos, cursor - 1 - startPos);
            if (content == "if") {
                kind = TokenKind::If;
            } else if (content == "else") {
                kind = TokenKind::Else;
            }
        } else {
            diagE(sourceCode, location.line, location.col, "current '%c' is illegal", curChar);
        }
    }
    currentToken = std::make_shared<Token>();
    currentToken->kind = kind;
    currentToken->value = value;
    currentToken->location = location;
    currentToken->content = sourceCode.substr(startPos, cursor - 1 - startPos);
}

bool C100::Lexer::isLetter() {
    return (curChar >= 'a' && curChar <= 'z') || (curChar >= 'A' && curChar <= 'Z') || (curChar == '_');
}

bool C100::Lexer::isDigit() {
    return curChar >= '0' && curChar <= '9';
}

bool C100::Lexer::isLetterOrDigit() {
    return isDigit() || isLetter();
}

void C100::Lexer::expectToken(C100::TokenKind kind) {
    if (currentToken->kind == kind) {
        getNextToken();
    } else {
        diagE(sourceCode, currentToken->location.line, currentToken->location.col, "%s expected",
              getTokenSimpleSpelling(kind));
    }
}

const char *C100::Lexer::getTokenSimpleSpelling(C100::TokenKind kind) {
    switch (kind) {
        case TokenKind::Add:
            return "+";
        case TokenKind::Sub:
            return "-";
        case TokenKind::Mul:
            return "*";
        case TokenKind::Div:
            return "/";
        case TokenKind::LParent:
            return "(";
        case TokenKind::RParent:
            return ")";
        case TokenKind::Semicolon:
            return ";";
        case TokenKind::Assign:
            return "=";
        case TokenKind::Eof:
            return "eof";
        default:
            break;
    }
    return 0;
}

char C100::Lexer::peekChar(int distance) {
    assert(distance >= 0);
    if (cursor - 1 + distance < sourceCode.size()) {
        return sourceCode[cursor - 1 + distance];
    } else {
        return '\0';
    }
}
